package com.electromagnetic.industry.software.manage.service.serviceimpl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.*;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.electromagnetic.industry.software.common.enums.*;
import com.electromagnetic.industry.software.common.exception.BizException;
import com.electromagnetic.industry.software.common.exception.PermissionDeniedException;
import com.electromagnetic.industry.software.common.pojo.ImportTableInfoVO;
import com.electromagnetic.industry.software.common.pojo.RespPageVO;
import com.electromagnetic.industry.software.common.resp.ElectromagneticResult;
import com.electromagnetic.industry.software.common.util.*;
import com.electromagnetic.industry.software.manage.config.ElePropertyConfig;
import com.electromagnetic.industry.software.manage.mapper.EdFileInfoMapper;
import com.electromagnetic.industry.software.manage.mapper.RoleMapper;
import com.electromagnetic.industry.software.manage.mapper.UserMapper;
import com.electromagnetic.industry.software.manage.pojo.models.*;
import com.electromagnetic.industry.software.manage.pojo.other.FileInfoVO;
import com.electromagnetic.industry.software.manage.pojo.other.UploadRecordDTO;
import com.electromagnetic.industry.software.manage.pojo.req.*;
import com.electromagnetic.industry.software.manage.pojo.resp.ChildFolderVO;
import com.electromagnetic.industry.software.manage.pojo.resp.FileProjectVO;
import com.electromagnetic.industry.software.manage.pojo.resp.FileVersionViewVO;
import com.electromagnetic.industry.software.manage.pojo.resp.UploadRecordVO;
import com.electromagnetic.industry.software.manage.service.*;
import com.electromagnetic.industry.software.manage.tasks.EleConstant;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

import static com.electromagnetic.industry.software.common.cons.ElectromagneticConstants.*;
import static com.electromagnetic.industry.software.common.enums.DataOwnEnum.USER_FILE;
import static com.electromagnetic.industry.software.common.enums.FileRepeatEnum.*;

@Slf4j
@Service
public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileInfo> implements EdFileInfoService {

    @Resource
    private CommonService commonService;
    @Resource
    private FileSystemService fileSystemService;
    @Resource
    private PermissionService permissionService;
    @Resource
    private UserMapper userMapper;
    @Resource
    private ElePropertyConfig elePropertyConfig;
    @Resource
    private EdFileFavoriteService fileFavoriteService;
    @Resource
    private FileTagRelationService fileTagRelationService;
    @Resource
    private UserAccessLogService userAccessLogService;
    @Resource
    private EdFileRelationService edFileRelationService;
    @Resource
    private RolePermissionService rolePermissionService;
    @Resource
    private RoleMapper roleMapper;
    @Resource
    private ImportTableService importTableService;


    /**
     * 查询文件列表
     *
     * @param pars
     * @return
     */
    @Override
    public ElectromagneticResult<?> queryEdFileInfo(FileInfoQueryDTO pars, int dataOwnCode) {
        ImportTableInfo importTableInfo = queryTableInfo(pars, dataOwnCode);
        if (Objects.nonNull(Optional.ofNullable(importTableInfo).map(ImportTableInfo::getTableHeader).orElse(null))) {
            ImportTableInfoVO importTableInfoVO = BeanUtil.toBean(importTableInfo, ImportTableInfoVO.class);
            return ElectromagneticResultUtil.success(new RespPageVO<>(importTableInfoVO));
        }
        return queryInfo(pars, dataOwnCode);
    }

    private ImportTableInfo queryTableInfo(FileInfoQueryDTO pars, int dataOwnCode) {
        String relatedId = pars.getParentId();
        return importTableService.selectByIdRelatedId(relatedId);
    }


    private ElectromagneticResult<?> queryInfo(FileInfoQueryDTO pars, int dataOwnCode) {

        if (DataOwnEnum.isSysCode(dataOwnCode)) {
            String parentId = pars.getParentId();
            List<String> accessibleTree = permissionService.getAccessibleTree();
            if ((dataOwnCode != USER_FILE.code) && (!accessibleTree.contains(parentId) && parentId.length() == elePropertyConfig.getPrjFolderMaxLength())) {
                throw new PermissionDeniedException();
            }
        }

        LambdaQueryWrapper<EdFileInfo> queryWrapper = Wrappers.lambdaQuery(EdFileInfo.class)
                .select(EdFileInfo.class, file -> !StrUtil.equals(file.getColumn(), "file_content"))
                .eq(EdFileInfo::getSaveStatus, EleDataSaveStatusEnum.SUCCESS.code)
                .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code)
                .eq(EdFileInfo::getParentId, pars.getParentId())
                .eq(EdFileInfo::getDataOwn, dataOwnCode)

                .eq(ObjUtil.equals(pars.getDataStatus(), EleDataStatusEnum.NOT_PUBLISHED.code), EdFileInfo::getDataStatus, EleDataStatusEnum.NOT_PUBLISHED.code)
                .eq(ObjUtil.equals(pars.getDataStatus(), EleDataStatusEnum.PUBLISHED.code), EdFileInfo::getDataStatus, EleDataStatusEnum.PUBLISHED.code)
                .eq(ObjUtil.equals(pars.getDataStatus(), EleDataStatusEnum.OCCUPY.code), EdFileInfo::getDataStatus, EleDataStatusEnum.OCCUPY.code)

                .eq(StrUtil.isNotEmpty(pars.getFileType()), EdFileInfo::getFileType, pars.getFileType())
                .orderByAsc(ObjUtil.equals(pars.getCreatedTime(), 0), EdFileInfo::getCreatedTime)
                .orderByDesc(ObjUtil.equals(pars.getCreatedTime(), 1), EdFileInfo::getCreatedTime)
                .orderByDesc(ObjUtil.isAllEmpty(pars.getCreatedTime(), pars.getVersionSort(), pars.getFileSizeSort(), pars.getFileNameSort(), pars.getUpdatedTime(), pars.getFileTypeSort()), EdFileInfo::getCreatedTime)
                .orderByDesc(ObjUtil.isAllEmpty(pars.getCreatedTime(), pars.getVersionSort(), pars.getFileSizeSort(), pars.getFileNameSort(), pars.getUpdatedTime(), pars.getFileTypeSort()), EdFileInfo::getCreatedTime)
                .orderByAsc(ObjUtil.equals(pars.getVersionSort(), 0), EdFileInfo::getFileVersion)
                .orderByDesc(ObjUtil.equals(pars.getVersionSort(), 1), EdFileInfo::getFileVersion)

                .orderByAsc(ObjUtil.equals(pars.getFileSizeSort(), 0), EdFileInfo::getFileSize)
                .orderByDesc(ObjUtil.equals(pars.getFileSizeSort(), 1), EdFileInfo::getFileSize)

                .orderByAsc(ObjUtil.equals(pars.getFileNameSort(), 0), EdFileInfo::getFileName)
                .orderByDesc(ObjUtil.equals(pars.getFileNameSort(), 1), EdFileInfo::getFileName)

                .orderByAsc(ObjUtil.equals(pars.getUpdatedTime(), 0), EdFileInfo::getUpdatedTime)
                .orderByDesc(ObjUtil.equals(pars.getUpdatedTime(), 1), EdFileInfo::getUpdatedTime)

                .orderByAsc(ObjUtil.equals(pars.getFileTypeSort(), 0), EdFileInfo::getSort)
                .orderByDesc(ObjUtil.equals(pars.getFileTypeSort(), 1), EdFileInfo::getSort);

        if (DataOwnEnum.isUserCode(dataOwnCode)) {
            queryWrapper.eq(EdFileInfo::getCreatedBy, UserThreadLocal.getUserId());
        }

        if (StrUtil.isNotEmpty(pars.getKeyword())) {
            queryWrapper.and(qr -> qr.like(EdFileInfo::getFileName, pars.getKeyword())
                    .or()
                    .like(EdFileInfo::getFileCode, pars.getKeyword())
                    .or()
                    .like(EdFileInfo::getFileNote, pars.getKeyword()));
//                    .or()
//                    .like(EdFileInfo::getFileContent, pars.getKeyword()));
        }

        // 处理 tagIds 查询
        if (CollUtil.isNotEmpty(pars.getTagIds())) {
            List<String> fileIdsWithTags = fileTagRelationService.getFileIdsByTagIds(pars.getTagIds());
            if (fileIdsWithTags.isEmpty()) {
                return ElectromagneticResultUtil.success(new RespPageVO<>(0, new ArrayList<>()));
            }
            queryWrapper.in(EdFileInfo::getFileId, fileIdsWithTags);
        }

        Page<EdFileInfo> edFileInfoPage = this.baseMapper.selectPage(new Page<>(pars.getPageNum(), pars.getPageSize()), queryWrapper);
        long total = edFileInfoPage.getTotal();
        List<FileInfoVO> records = BeanUtil.copyToList(edFileInfoPage.getRecords(), FileInfoVO.class);
        records.forEach(e -> {
            e.setFileSizeShow(EleCommonUtil.convertFileSize(e.getFileSize()));
            e.setIsFavorite(isFavorite(UserThreadLocal.getUserId(), e.getId()) ? 1 : 0);
            e.setLabels(fileTagRelationService.getFileTags(e.getFileId()));
        });
        UserThreadLocal.setSuccessInfo("", "", "查询文件成功");
        return ElectromagneticResultUtil.success(new RespPageVO<>(total, records));
    }


    /**
     * 新建文件夹
     *
     * @return
     */
    @Override
    public ElectromagneticResult<?> createFolder(CreateFolderDTO createFolderDTO, int dataOwnCode) {
        Assert.isTrue(EleCommonUtil.isFileNameValid(createFolderDTO.getNewFolderName()), NAME_VALID_MSG);
        String folderId = EleIdCreator.createSnowflakeId();

        if (DataOwnEnum.isSysCode(dataOwnCode) || DataOwnEnum.isRepoCode(dataOwnCode)) {
            List<String> accessibleTree = permissionService.getAccessibleTree();
            if (!accessibleTree.contains(createFolderDTO.getParentId())) {
                return ElectromagneticResultUtil.fail("-1", "没有操作权限。");
            }
        }
        ElectromagneticResult<?> res = commonService.addFolder(createFolderDTO.getParentId(), createFolderDTO.getNewFolderName(), false, folderId, createFolderDTO.getFileNote(), dataOwnCode);
        String dbPath = commonService.getDbPath(this.baseMapper.selectById(createFolderDTO.getParentId()).getFilePath());
        UserThreadLocal.setSuccessInfo(createFolderDTO.getParentId(), res.getData() + "", "创建文件夹 {} 成功，路径为 {}", createFolderDTO.getNewFolderName(), dbPath);
        return res;
    }

    /**
     * 项目层级结构查询
     *
     * @return
     */
    @Override
    public ElectromagneticResult<?> tree(int querySource) {

        /**
         * 都是从用户界面处进来，一个是从数据库的用户界面（SYS_DB），一个是从用户自己的工程界面处（USER_DB），一个是从库工程界面进入（REPO_DB)。
         */

        List<String> accessibleIds;

        if (querySource == PrjQuerySource.SYS_DB.value || querySource == PrjQuerySource.REPO_DB.value) {
            accessibleIds = permissionService.getAccessibleTree();
            if (CollUtil.isEmpty(accessibleIds)) {
                UserThreadLocal.setSuccessInfo("", "", "查询项目层级结构成功");
                return ElectromagneticResultUtil.success(new ArrayList<>());
            }
        } else {
            accessibleIds = this.baseMapper.selectList(Wrappers.lambdaQuery(EdFileInfo.class)
                    .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code)
                    .eq(EdFileInfo::getDataOwn, DataOwnEnum.USER_PRJ.code)
                    .eq(EdFileInfo::getCreatedBy, UserThreadLocal.getUserId())
                    .eq(EdFileInfo::getParentId, PRJ_PARENT_ID)).stream().map(EdFileInfo::getId).toList();
        }
        Map<Integer, List<String>> map;
        if (querySource == PrjQuerySource.SYS_DB.value) {
            map = commonService.querySysPrjTree(querySource, accessibleIds, new FileProjectVO(), null);
        } else if (querySource == PrjQuerySource.USER_DB.value) {
            map = commonService.queryUserPrjTree(querySource, new FileProjectVO(), null);
        } else {
            map = commonService.queryRepoPrjTree(querySource, accessibleIds, new FileProjectVO(), null);
        }
        List<String> strings = map.getOrDefault(querySource, new ArrayList<>());
        List<FileProjectVO> res = new ArrayList<>();
        strings.forEach(e -> {
            List<FileProjectVO> list = JSONUtil.toList(e, FileProjectVO.class);
            if (CollUtil.isNotEmpty(list)) {
                FileProjectVO fileProjectVO = list.get(0);
                res.add(fileProjectVO);
            }
        });
        res.sort(Comparator.comparing(FileProjectVO::getSort));
        UserThreadLocal.setSuccessInfo("", "", "查询项目层级结构成功");
        return ElectromagneticResultUtil.success(res);

    }

    /**
     * 删除目录
     *
     * @param id
     * @return
     */
    @Override
    public ElectromagneticResult<?> delete(String id, int dataOwnCode) {
        EdFileInfo fileInfo = this.baseMapper.selectById(id);
        String dbPath = commonService.getDbPath(fileInfo.getFilePath());
        if (fileInfo.getDataType() == EleDataTypeEnum.FOLDER.code) {
            ElectromagneticResult<?> res = commonService.deleteFolder(id);
            UserThreadLocal.setSuccessInfo(fileInfo.getParentId(), id, "作废目录 {} 成功，路径为 {}", fileInfo.getFileName(), dbPath);
            return res;
        }
        this.baseMapper.update(new EdFileInfo(), Wrappers.lambdaUpdate(EdFileInfo.class)
                .set(EdFileInfo::getEffectFlag, false)
                .set(EdFileInfo::getAllDeleted, true)
                .eq(EdFileInfo::getFileId, fileInfo.getFileId()));
        // 统一处理文件相关关系
        cleanRelatedData(fileInfo.getFileId());
        UserThreadLocal.setSuccessInfo(fileInfo.getParentId(), id, "作废文件 {}.{} 成功，路径为 {}", fileInfo.getFileName(), fileInfo.getFileType(), dbPath);
        return ElectromagneticResultUtil.success(true);
    }

    /**
     * 下载文件
     *
     * @param id
     * @return
     */
    @Override
    public ResponseEntity<InputStreamResource> download(String id, HttpServletResponse response, int dataOwnCode) {
        String fileName = "";
        EdFileInfo fileInfo = this.baseMapper.selectById(id);
        String fileSysPath = commonService.getFileSysPath(id);
        String dbPath = commonService.getDbPath(fileInfo.getFilePath());
        try {
            Assert.isTrue(FileUtil.exist(fileSysPath), "下载文件不存在");
            FileSystemResource fileSystemResource = new FileSystemResource(fileSysPath);
            fileName = fileSystemResource.getFilename();
            HttpHeaders headers = new HttpHeaders();
            headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
            headers.add("Pragma", "no-cache");
            headers.add("Expires", "0");
            String newFileName = Base64.encode(fileInfo.getFileName() + "." + fileInfo.getFileType());
            response.setHeader("content-disposition", "attachment;filename=" + newFileName);
            UserThreadLocal.setSuccessInfo(fileInfo.getParentId(), fileInfo.getFileId(), "下载文件 {} 成功，文件路径 {}", fileName, dbPath);
            this.baseMapper.update(Wrappers.lambdaUpdate(EdFileInfo.class)
                    .set(EdFileInfo::getDataStatus, EleDataStatusEnum.OCCUPY.code)
                    .set(EdFileInfo::getUpdatedTime, new Date())
                    .eq(EdFileInfo::getId, id));
            // 构建响应实体(可以返回<byte[]或Resource，返回类型取决body入参类型)
            return ResponseEntity
                    .ok()
                    .headers(headers)
                    .contentLength(fileSystemResource.contentLength())
                    .contentType(MediaType.parseMediaType("application/octet-stream;charset=UTF-8"))
                    .body(new InputStreamResource(fileSystemResource.getInputStream()));
        } catch (Exception e) {
            String info = StrFormatter.format("下载文件异常 {}，路径 {}", fileName, dbPath);
            log.error(info, e);
            throw new BizException(info);
        } finally {
            EleConstant.add(id);
        }
    }

    /**
     * 更新文件信息
     *
     * @param updateFileInfoDTO
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ElectromagneticResult<?> updateFileInfo(UpdateFileInfoDTO updateFileInfoDTO, int dataOwnCode) {
        String newFileName = updateFileInfoDTO.getFileName();
        Assert.isTrue(EleCommonUtil.isFileNameValid(newFileName), StrFormatter.format("{} {}", newFileName, NAME_VALID_MSG));
        try {
            // 首先检查新名称是否存在
            EdFileInfo fileInfo = this.baseMapper.selectById(updateFileInfoDTO.getId());
            String parentId = fileInfo.getParentId();
            // 是更新文件还是文件夹
            LambdaQueryWrapper<EdFileInfo> queryWrapper = Wrappers.<EdFileInfo>lambdaQuery()
                    .select(EdFileInfo::getFileName)
                    .eq(EdFileInfo::getParentId, parentId)
                    .eq(EdFileInfo::getDataType, EleDataTypeEnum.FOLDER.code)
                    .eq(EdFileInfo::getFileName, updateFileInfoDTO.getFileName())
                    .eq(EdFileInfo::getFileType, fileInfo.getFileType())
                    .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code);
            if (fileInfo.getDataType().equals(EleDataTypeEnum.FOLDER.code)) {
                queryWrapper.eq(EdFileInfo::getDataType, EleDataTypeEnum.FOLDER.code);
            } else {
                queryWrapper.eq(EdFileInfo::getDataType, EleDataTypeEnum.FILE.code);
            }
            List<EdFileInfo> edFileInfos = this.baseMapper.selectList(Wrappers.<EdFileInfo>lambdaQuery()
                    .select(EdFileInfo::getId, EdFileInfo::getFileName)
                    .eq(EdFileInfo::getParentId, parentId)
                    .eq(EdFileInfo::getFileName, updateFileInfoDTO.getFileName())
                    .eq(EdFileInfo::getFileType, fileInfo.getFileType())
                    .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
            Map<String, EdFileInfo> map = edFileInfos.stream().collect(Collectors.toMap(EdFileInfo::getId, e -> e));
            if (map.size() == 1) {
                if (!map.values().iterator().next().getId().equals(updateFileInfoDTO.getId())) {
                    return ElectromagneticResultUtil.fail("-1", StrFormatter.format("文件名 {} 已经存在", newFileName));
                }
            } else if (map.size() > 1) {
                log.warn("数据库中存在了两份同名同后缀的文件");
                throw new BizException("文件名已经存在");
            }

            String dbPath = commonService.getDbPath(fileInfo.getFilePath());
            this.baseMapper.update(new EdFileInfo(), Wrappers.lambdaUpdate(EdFileInfo.class)
                    .eq(EdFileInfo::getId, updateFileInfoDTO.getId())
                    .set(EdFileInfo::getFileName, updateFileInfoDTO.getFileName())
                    .set(EdFileInfo::getFileNote, updateFileInfoDTO.getFileNote()));
            UserThreadLocal.setSuccessInfo(commonService.getLastPrjLeafId(fileInfo.getFilePath()), updateFileInfoDTO.getId(), "更新文件信息成功，新文件名为 {}.{}，新备注为 {}，路径为 {}", newFileName, fileInfo.getFileType(), updateFileInfoDTO.getFileNote(), dbPath);
            return ElectromagneticResultUtil.success(true);
        } catch (Exception e) {
            String info = StrFormatter.format("更新文件信息失败，新文件名 {}， 原因 {}", newFileName, e.getMessage());
            log.error(info, e);
            throw new BizException(info);
        }
    }

    /**
     * 版本回退
     *
     * @param fileId
     * @param targetVersion
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ElectromagneticResult<?> versionBack(String fileId, int targetVersion) {
        EdFileInfo fileInfo = this.baseMapper.selectList(Wrappers.<EdFileInfo>lambdaQuery().eq(EdFileInfo::getFileId, fileId).last("limit 1")).get(0);
        try {
            this.baseMapper.update(new EdFileInfo(), Wrappers.lambdaUpdate(EdFileInfo.class)
                    .eq(EdFileInfo::getFileId, fileId)
                    .set(EdFileInfo::getEffectFlag, false));
            this.baseMapper.update(new EdFileInfo(), Wrappers.lambdaUpdate(EdFileInfo.class)
                    .set(EdFileInfo::getEffectFlag, true)
                    .eq(EdFileInfo::getFileId, fileId)
                    .eq(EdFileInfo::getFileVersion, targetVersion));
        } catch (Exception e) {
            String info = "版本回退失败，新版本为" + targetVersion;
            log.error(info, e);
            throw new BizException(info);
        }
        String dbPath = commonService.getDbPath(fileInfo.getFilePath());
        UserThreadLocal.setSuccessInfo(fileInfo.getParentId(), fileId, "回退版本成功，新版本为 {}，路径为 {}", targetVersion, dbPath);
        return ElectromagneticResultUtil.success(true);
    }

    /**
     * 查看分片是否存在
     *
     * @param fileChunkDTO
     * @return
     */
    @Override
    public ElectromagneticResult<?> checkChunkExist(FileChunkDTO fileChunkDTO, int dataOwnCode) {
        String currentUserId = UserThreadLocal.getUserId();
        String userUploadFolder = elePropertyConfig.getUploadDataDir(dataOwnCode) + File.separator + currentUserId;
        String identifier = fileChunkDTO.getIdentifier();
        List<Integer> uploadedChunks = getUploadedChunks(identifier, userUploadFolder);
        return ElectromagneticResultUtil.success(new FileChunkResultDTO(false, new HashSet<>(uploadedChunks)));
    }

    private List<Integer> getUploadedChunks(String identifier, String destPath) {
        String filePath = destPath + File.separator + identifier;
        if (!FileUtil.exist(new File(filePath))) {
            return new ArrayList<>();
        }

        return FileUtil.listFileNames(destPath)
                .stream()
                .map(e -> e.replace(UPLOAD_FILE_CHUNK_SUFFIX, ""))
                .map(Integer::parseInt)
                .toList();
    }

    /**
     * 批量导入
     *
     * @param fileChunkDTO
     * @return
     */
    @Override
    public ElectromagneticResult<?> batchImport(FileChunkDTO fileChunkDTO, int dataOwnCode) {
        String currentUserId = UserThreadLocal.getUserId();
        String identifier = fileChunkDTO.getIdentifier();
        // 首先检查该分片有没被上传，如果有则禁止上传
        String destPath = elePropertyConfig.getUploadDataDir(dataOwnCode) + File.separator + currentUserId + File.separator + identifier + File.separator + fileChunkDTO.getChunkNumber() + UPLOAD_FILE_CHUNK_SUFFIX;
        boolean exist = FileUtil.exist(destPath);
        if (exist) {
            return ElectromagneticResultUtil.fail("-1", "文件已经存在，请勿重复上传");
        }

        try {
            FileUtil.writeFromStream(fileChunkDTO.getFile().getInputStream(), destPath);
        } catch (IOException ioException) {
            log.error("上传文件失败...", ioException);
            throw new BizException("上传文件失败");
        }
        return ElectromagneticResultUtil.success(fileChunkDTO.getIdentifier());
    }

    /**
     * 合并分片
     * case1: 用户上传的新文件
     * case2: 用户上传的带版本号的文件
     * case3: 用户上传的文件在线上已经被移动到其他目录
     *
     * @param identifier
     * @param fileName
     * @param totalChunks
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ElectromagneticResult<?> mergeChunks(String identifier, String fileName, Integer totalChunks, int dataOwnCode) {
        String currentUserId = UserThreadLocal.getUserId();
        String destColibPath = doSysFileMerge(identifier, fileName, totalChunks, dataOwnCode);
        String mainName = FileUtil.mainName(destColibPath);
        String parentDir = FileUtil.getParent(destColibPath, 1);
        String zipDirPath = parentDir + File.separator + mainName + ".zip";
        AES aes = SecureUtil.aes(elePropertyConfig.getFileEncPasswd().getBytes());
        try (
                InputStream inputStream = Files.newInputStream(Paths.get(destColibPath));
                OutputStream outputStream = Files.newOutputStream(Paths.get(zipDirPath))
        ) {
            aes.decrypt(inputStream, outputStream, true);
            String uuid = IdUtil.fastSimpleUUID();
            String tmpDir = elePropertyConfig.getUploadDataDir(dataOwnCode) + currentUserId + File.separator + uuid + File.separator;
            ZipUtil.unzip(zipDirPath, tmpDir);
            update2Database(tmpDir, dataOwnCode);
            UserThreadLocal.setSuccessInfo("", "", "导入数据库成功，文件名称为 " + mainName);
            return ElectromagneticResultUtil.success(true);
        } catch (Exception e) {
            String info = "文件上传错误";
            log.error(info, e);
            throw new BizException(info);
        } finally {
            fileSystemService.deleteFile(zipDirPath, destColibPath);
        }
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void update2Database(String prjDirPath, int dataOwnCode) {
        updatePrjInfo(prjDirPath, dataOwnCode);
        updateCollectionInfo(prjDirPath);
        updateFileTageInfo(prjDirPath);
        updateFileRelationInfo(prjDirPath);
        updateUserAccessLog(prjDirPath);
    }

    private void updateUserAccessLog(String prjDirPath) {
        String path = prjDirPath + File.separator + USER_ACCESS_LOG + ".json";
        List<UserAccessLog> userAccessLogs = JSONUtil.toList(FileUtil.readString(path, Charset.defaultCharset()), UserAccessLog.class);
        userAccessLogService.saveOrUpdateBatch(userAccessLogs);
    }

    private void updateFileRelationInfo(String prjDirPath) {
        // UNIQUE KEY id1 + id2  prd没有做详细说明，此处先按照最新修改时间原则来做
        String path = prjDirPath + File.separator + ED_FILE_RELATION + ".json";
        List<EdFileRelation> relations = JSONUtil.toList(FileUtil.readString(path, Charset.defaultCharset()), EdFileRelation.class);
        for (EdFileRelation importRelation : relations) {
            EdFileRelation dbRelation = edFileRelationService.getBaseMapper().selectOne(Wrappers.lambdaQuery(EdFileRelation.class)
                    .eq(EdFileRelation::getId1, importRelation.getId())
                    .eq(EdFileRelation::getId2, importRelation.getId2()));
            if (Objects.isNull(dbRelation)) {
                edFileRelationService.saveOrUpdate(importRelation);
            } else {
                // 如果导入的比线上的新，则新增
                if (importRelation.getUpdatedTime().after(dbRelation.getUpdatedTime())) {
                    edFileRelationService.saveOrUpdate(importRelation);
                }
            }
        }
    }

    private void updateFileTageInfo(String prjDirPath) {
        // UNIQUE KEY fileId + tagId
        String path = prjDirPath + File.separator + ED_TAG_RELATIONS + ".json";
        List<FileTagRelation> relations = JSONUtil.toList(FileUtil.readString(path, Charset.defaultCharset()), FileTagRelation.class);
        for (FileTagRelation importRelation : relations) {
            FileTagRelation dbRelation = fileTagRelationService.getBaseMapper().selectOne(Wrappers.lambdaQuery(FileTagRelation.class)
                    .eq(FileTagRelation::getTagId, importRelation.getId())
                    .eq(FileTagRelation::getFileId, importRelation.getFileId()));
            if (Objects.isNull(dbRelation)) {
                fileTagRelationService.saveOrUpdate(importRelation);
            } else {
                if (!Objects.equals(importRelation.getEffectFlag(), dbRelation.getEffectFlag())) {
                    dbRelation.setEffectFlag(EffectFlagEnum.NOT_EFFECTIVE.code);
                    fileTagRelationService.saveOrUpdate(dbRelation);
                }
            }
        }
    }

    private void updateCollectionInfo(String prjDirPath) {
        // UNIQUE KEY  userId + fileId
        String path = prjDirPath + File.separator + ED_FILE_FAVORITE + ".json";
        List<EdFileFavorite> edFileFavorites = JSONUtil.toList(FileUtil.readString(path, Charset.defaultCharset()), EdFileFavorite.class);
        String userId = UserThreadLocal.getUserId();
        for (EdFileFavorite importFileFavorite : edFileFavorites) {
            EdFileFavorite dbFileFavorite = fileFavoriteService.getBaseMapper().selectOne(Wrappers.lambdaQuery(EdFileFavorite.class)
                    .eq(EdFileFavorite::getCreatedBy, userId)
                    .eq(EdFileFavorite::getFileId, importFileFavorite.getFileId()));
            if (Objects.isNull(dbFileFavorite)) {
                fileFavoriteService.saveOrUpdate(importFileFavorite);
            } else {
                if (importFileFavorite.getUpdatedTime().after(dbFileFavorite.getUpdatedTime())) {
                    dbFileFavorite.setEffectFlag(importFileFavorite.getEffectFlag());
                    fileFavoriteService.saveOrUpdate(dbFileFavorite);
                }
            }
        }
    }

    private void updatePrjInfo(String prjDirPath, Integer dataOwnCode) {
        String path = prjDirPath + File.separator + PRJ_INFO + ".json";
        List<EdFileInfo> importAllFiles = JSONUtil.toList(FileUtil.readString(path, Charset.defaultCharset()), EdFileInfo.class);
        // 找出层级文件夹
        List<EdFileInfo> prjFolders = importAllFiles.stream().filter(e -> DataOwnEnum.isPrjCode(e.getDataOwn()))
                .toList();
        // 找出用户创建的文件夹
        List<EdFileInfo> userFolders = importAllFiles.stream().filter(e -> ObjUtil.equals(e.getDataType(), EleDataTypeEnum.FOLDER.code))
                .filter(e -> DataOwnEnum.isFileCode(e.getDataOwn()))
                .toList();
        // 找出所有文件
        List<EdFileInfo> allFiles = importAllFiles.stream().filter(e -> ObjUtil.equals(e.getDataType(), EleDataTypeEnum.FILE.code)).toList();

        Set<EdFileInfo> allObjs = new HashSet<>(prjFolders);
        // 处理用户创建的文件夹
        for (EdFileInfo edFileInfo : userFolders) {
            String fileName = edFileInfo.getFileName();
            String parentId = edFileInfo.getParentId();
            List<EdFileInfo> folders = queryChildFolders(parentId);
            Map<String, EdFileInfo> foldersMap = folders.stream().collect(Collectors.toMap(EdFileInfo::getFileName, e -> e));
            List<String> folderNames = folders.stream().map(EdFileInfo::getFileName).toList();
            if (folderNames.contains(fileName)) { // 有同名文件夹
                // 判断id是否相同
                if (foldersMap.get(fileName).getId().equals(edFileInfo.getId())) { // id相同，不做处理
                    log.info("id相同，不做处理");
                } else { // 文件名重复，导入的文件名需要添加“_1”
                    resetFolderInfo(edFileInfo);
                    allObjs.add(edFileInfo);
                }
            } else { // 没有同名文件夹
                allObjs.add(edFileInfo);
            }
        }
        // 处理文件
        Map<String, List<EdFileInfo>> fileInfoMap = new HashMap<>();
        for (EdFileInfo edFileInfo : allFiles) {
            List<EdFileInfo> list = fileInfoMap.getOrDefault(edFileInfo.getFileId(), new ArrayList<>());
            list.add(edFileInfo);
            fileInfoMap.put(edFileInfo.getFileId(), list);
        }
        for (Map.Entry<String, List<EdFileInfo>> entry : fileInfoMap.entrySet()) {
            String fileId = entry.getKey();
            List<EdFileInfo> importFiles = entry.getValue();
            importFiles.sort(Comparator.comparing(EdFileInfo::getCreatedTime));
            List<EdFileInfo> dbFileInfos = this.baseMapper.selectList(Wrappers.lambdaQuery(EdFileInfo.class).eq(EdFileInfo::getFileId, fileId).orderByAsc(EdFileInfo::getFileVersion));
            // 在线上没有找到，则该批文件是从线下上传的
            if (CollUtil.isEmpty(dbFileInfos)) {
                EdFileInfo fileInfo = importFiles.stream().filter(e -> e.getEffectFlag().equals(EffectFlagEnum.EFFECT.code)).findFirst().get();
                resetFileInfoName(fileInfo);
                allObjs.addAll(importFiles);
                allObjs.add(fileInfo);
                // 线下和线上都存在
            } else {
                Map<String, EdFileInfo> dbIdMap = dbFileInfos.stream().collect(Collectors.toMap(EdFileInfo::getId, e -> e));
                Map<String, EdFileInfo> importIdMap = importFiles.stream().collect(Collectors.toMap(EdFileInfo::getId, e -> e));

                importIdMap.putAll(dbIdMap);

                List<EdFileInfo> deepCopyImport = JSONUtil.toList(JSONUtil.toJsonStr(importFiles), EdFileInfo.class);
                List<EdFileInfo> deepCopyDb = JSONUtil.toList(JSONUtil.toJsonStr(dbFileInfos), EdFileInfo.class);

                // 导入的文件在线下已经被废除了，不同步到线上
                Optional<EdFileInfo> first = deepCopyImport.stream().filter(e -> e.getEffectFlag().equals(EffectFlagEnum.EFFECT.code)).findFirst();
                if (first.isEmpty()) {
                    continue;
                }
                EdFileInfo importEffectFile = first.get();
                Optional<EdFileInfo> first1 = deepCopyDb.stream().filter(e -> e.getEffectFlag().equals(EffectFlagEnum.EFFECT.code)).findFirst();
                EdFileInfo dbEffectFile = null;
                if (first1.isEmpty()) {
                    // 线下存在，线上不存在，被废除了，此时根据更新时间判断，如果线上的比线下的新，则以线上的为准
                    EdFileInfo fileInfo = deepCopyDb.stream().max(Comparator.comparing(EdFileInfo::getUpdatedTime)).get();
                    if (fileInfo.getUpdatedTime().after(importEffectFile.getUpdatedTime())) {
                        continue;
                    }
                } else {
                    dbEffectFile = first1.get();
                }

                // 如果导入的时间比数据库的时间新，则将导入的置为effect
                String effectId;
                // 如果数据库中的有效文件为null，则使用导入的
                if (ObjUtil.isNull(dbEffectFile)) {
                    effectId = importEffectFile.getId();
                } else {
                    effectId = importEffectFile.getUpdatedTime().after(dbEffectFile.getUpdatedTime()) ? importEffectFile.getId() : dbEffectFile.getId();
                }
                Map<String, String> importVersionRelation = getVersionRelation(deepCopyImport);
                Map<String, String> dbVersionRelation = getVersionRelation(deepCopyDb);
                dbVersionRelation.putAll(importVersionRelation);

                //**************************
                int start = FILE_START_VERSION;
                List<EdFileInfo> saveObjs = new ArrayList<>();
                importFiles = importFiles.stream().distinct().toList();
                for (EdFileInfo importFileInfo : importFiles) {
                    // 该文件在数据库中存在，是从线上导出的。
                    if (dbIdMap.containsKey(importFileInfo.getId())) {
                        EdFileInfo dbFileInfo = dbIdMap.get(importFileInfo.getId());
                        // 线下版本的修改时间比线上的新，用线下的版本
                        if (dbFileInfo.getUpdatedTime().before(importFileInfo.getUpdatedTime())) {
                            importFileInfo.setFileVersion(start);

                            saveObjs.add(importFileInfo);
                            dbIdMap.remove(importFileInfo.getId());
                            // 线上版本的修改时间比线下新，用线上的版本。
                        } else {
                            dbFileInfo.setFileVersion(start);
                            saveObjs.add(dbFileInfo);
                            dbIdMap.remove(dbFileInfo.getId());
                        }
                        // 该文件是线下上传的。
                    } else {
                        importFileInfo.setFileVersion(start);
                        saveObjs.add(importFileInfo);
                        dbIdMap.remove(importFileInfo.getId());
                    }
                    ++start;
                }
                // 添加数据库中剩余的
                for (EdFileInfo edFileInfo : dbIdMap.values()) {
                    edFileInfo.setFileVersion(start);
                    saveObjs.add(edFileInfo);
                    ++start;
                }
                //*************************
                for (EdFileInfo saveObj : saveObjs) {
                    String id = saveObj.getId();
                    String preVersionId = dbVersionRelation.get(id);
                    EdFileInfo tmp = importIdMap.get(preVersionId);
                    saveObj.setPreVersion(Optional.ofNullable(tmp).map(EdFileInfo::getFileVersion).orElse(null));
                    int effect = ObjUtil.equals(saveObj.getId(), effectId) ? EffectFlagEnum.EFFECT.code : EffectFlagEnum.NOT_EFFECTIVE.code;
                    saveObj.setEffectFlag(effect);
                }
                allObjs.addAll(saveObjs);
            }
        }
        this.saveOrUpdateBatch(allObjs);
        update2FileSystem(allObjs, prjDirPath, dataOwnCode);
    }

    @Override
    public void resetFileInfoName(EdFileInfo fileInfo) {
        String fileName = fileInfo.getFileName();
        String parentId = fileInfo.getParentId();
        for (int i = 0; i < 1000; ++i) {
            long count = this.baseMapper.selectCount(Wrappers.lambdaQuery(EdFileInfo.class)
                    .eq(EdFileInfo::getFileName, fileName)
                    .eq(EdFileInfo::getFileType, fileInfo.getFileType())
                    .eq(EdFileInfo::getParentId, parentId)
                    .eq(EdFileInfo::getDataType, EleDataTypeEnum.FILE.code)
                    .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
            if (count > 0) {
                fileName = fileName + APPEND_NEW_FILE_NAME;
            } else {
                fileInfo.setFileName(fileName);
                return;
            }
        }
    }

    public void resetFolderInfo(EdFileInfo fileInfo) {
        String fileName = fileInfo.getFileName();
        String parentId = fileInfo.getParentId();
        for (int i = 0; i < 1000; ++i) {
            long count = this.baseMapper.selectCount(Wrappers.lambdaQuery(EdFileInfo.class)
                    .eq(EdFileInfo::getFileName, fileName)
                    .eq(EdFileInfo::getParentId, parentId)
                    .eq(EdFileInfo::getDataType, EleDataTypeEnum.FOLDER.code)
                    .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
            if (count > 0) {
                fileName = fileName + APPEND_NEW_FILE_NAME;
            } else {
                fileInfo.setFileName(fileName);
                return;
            }
        }
    }

    private void update2FileSystem(Set<EdFileInfo> needMove2FileSystemFiles, String prjDirPath, int dataOwnCode) {
        List<EdFileInfo> files = needMove2FileSystemFiles.stream().filter(e -> e.getDataType().equals(EleDataTypeEnum.FILE.code)).toList();
        List<File> files1 = Arrays.stream(Objects.requireNonNull(new File(prjDirPath).listFiles())).filter(e -> e.isDirectory() && e.getName().startsWith(EXPORT_PRJ_NAME)).toList();
        String prjFilePath = files1.get(0).getAbsolutePath();
        for (EdFileInfo edFileInfo : files) {
            String id = edFileInfo.getId();
            String sourcePath = prjFilePath + File.separator + id;
            String destPath = commonService.getPrjRootPath1(dataOwnCode) + File.separator + id;
            fileSystemService.moveFile(sourcePath, destPath);
            log.info("file import to file system source path is ----> {}, dest path is --->{}", sourcePath, destPath);
        }
    }

    private Map<String, String> getVersionRelation(List<EdFileInfo> edFileInfos) {
        Map<String, String> versionRelation = new HashMap<>();
        Map<Integer, EdFileInfo> versionMap = edFileInfos.stream().collect(Collectors.toMap(EdFileInfo::getFileVersion, e -> e));
        for (EdFileInfo edFileInfo : edFileInfos) {
            Integer preVersion = edFileInfo.getPreVersion();
            EdFileInfo fileInfo = versionMap.get(preVersion);
            String preVersionId = ObjUtil.isNull(fileInfo) ? null : fileInfo.getId();
            versionRelation.put(edFileInfo.getId(), preVersionId);
        }
        return versionRelation;
    }

    private List<EdFileInfo> queryChildFolders(String parentId) {
        return this.baseMapper.selectList(Wrappers.lambdaQuery(EdFileInfo.class).eq(EdFileInfo::getParentId, parentId)
                .eq(EdFileInfo::getDataType, EleDataTypeEnum.FOLDER.code)
                .eq(EdFileInfo::getEffectFlag, true));
    }

    /**
     * 导出
     *
     * @param dataIdArr
     * @return
     */
    @Override
    public ResponseEntity<InputStreamResource> batchExport(String dataIdArr, HttpServletResponse response, int dataOwnCode) throws IOException {

        String[] ids = dataIdArr.split(",");
        String userDownloadDataDir = elePropertyConfig.getDownloadDataDir(dataOwnCode) + File.separator + UserThreadLocal.getUserId();
        if (DataOwnEnum.isSysCode(dataOwnCode) || DataOwnEnum.isRepoCode(dataOwnCode)) {
            Map<String, Boolean> map = permissionService.filterExportIds(ids);
            Assert.isTrue(!map.containsValue(Boolean.FALSE), "有未授权的层级目录，禁止导出");
        }

        String nowTimeStr = EleCommonUtil.getNowTimeStr();

        // 导出工程目录信息
        List<String> exportFileIds = exportPrjInfo(nowTimeStr, dataOwnCode, dataIdArr, userDownloadDataDir);
        // 导出文件收藏相关信息
        exportCollectionInfo(nowTimeStr, exportFileIds, userDownloadDataDir);
        // 导出文件关系
        exportFileRelationInfo(nowTimeStr, exportFileIds, userDownloadDataDir);
        // 导出标签相关信息
        exportFileTagInfo(nowTimeStr, exportFileIds, userDownloadDataDir);
        // 导出操作记录相关
        exportLogInfo(nowTimeStr, userDownloadDataDir);

        String prjDirPath = userDownloadDataDir + File.separator + EXPORT_PRJ_NAME + "_" + nowTimeStr;
        String exportZipFile = userDownloadDataDir + File.separator + EXPORT_PRJ_NAME + "_" + nowTimeStr + ".zip";
        String exportColibFile = userDownloadDataDir + File.separator + File.separator + EXPORT_PRJ_NAME + "_" + nowTimeStr + EXPORT_FILE_SUFFIX;
        FileUtil.del(exportColibFile);
        ZipUtil.zip(prjDirPath, exportZipFile);
        AES aes = SecureUtil.aes(elePropertyConfig.getFileEncPasswd().getBytes());
        try (
                InputStream inputStream = Files.newInputStream(Paths.get(exportZipFile));
                OutputStream outputStream = Files.newOutputStream(Paths.get(exportColibFile))
        ) {
            aes.encrypt(inputStream, outputStream, true);
        } catch (Exception e) {
            String info = "导出失败。";
            log.error(info, e);
            throw new BizException(info);
        } finally {
            fileSystemService.deleteFile(exportZipFile, prjDirPath);
        }
        File file = FileUtil.newFile(exportColibFile);
        FileSystemResource fileSystemResource = new FileSystemResource(file);
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        String tmpFileName = fileSystemResource.getFilename();
        String fileName = Base64.encode(tmpFileName);
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        response.setHeader("content-disposition", "attachment;filename=" + fileName);
        UserThreadLocal.setSuccessInfo("", "", "导出数据库成功，文件名称为" + tmpFileName);
        // 构建响应实体(可以返回<byte[]或Resource，返回类型取决body入参类型)
        return ResponseEntity
                .ok()
                .headers(headers)
                .contentLength(fileSystemResource.contentLength())
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .body(new InputStreamResource(fileSystemResource.getInputStream()));
    }

    private void exportLogInfo(String nowTimeStr, String userDownloadDataDir) {
        List<UserAccessLog> userAccessLogs = userAccessLogService.getBaseMapper().selectList(null);
        String json = JSONUtil.toJsonStr(userAccessLogs);
        String path = userDownloadDataDir + File.separator + EXPORT_PRJ_NAME + "_" + nowTimeStr + File.separator + USER_ACCESS_LOG + ".json";
        fileSystemService.writeStringToFile(path, json);
    }

    private void exportFileTagInfo(String nowTimeStr, List<String> exportFileIds, String userDownloadDataDir) {
        List<FileTagRelation> fileTagRelations = fileTagRelationService.getBaseMapper().selectList(Wrappers.lambdaQuery(FileTagRelation.class)
                .in(FileTagRelation::getFileId, exportFileIds));
        String path = userDownloadDataDir + File.separator + EXPORT_PRJ_NAME + "_" + nowTimeStr + File.separator + ED_TAG_RELATIONS + ".json";
        String json = JSONUtil.toJsonStr(fileTagRelations);
        fileSystemService.writeStringToFile(path, json);
    }

    private void exportFileRelationInfo(String nowTimeStr, List<String> exportFileIds, String userDownloadDataDir) {
        List<EdFileRelation> edFileRelations = edFileRelationService.getBaseMapper().selectList(Wrappers.lambdaQuery(EdFileRelation.class).in(EdFileRelation::getId1, exportFileIds)
                .or()
                .in(EdFileRelation::getId2, exportFileIds));
        String json = JSONUtil.toJsonStr(edFileRelations);
        String path = userDownloadDataDir + File.separator + EXPORT_PRJ_NAME + "_" + nowTimeStr + File.separator + ED_FILE_RELATION + ".json";
        fileSystemService.writeStringToFile(path, json);
    }

    private void exportCollectionInfo(String nowTimeStr, List<String> exportFileIds, String userDownloadDataDir) {
        List<EdFileFavorite> edFileFavorites = fileFavoriteService.getBaseMapper().selectList(Wrappers.lambdaQuery(EdFileFavorite.class)
                .in(EdFileFavorite::getFileId, exportFileIds));
        String json = JSONUtil.toJsonStr(edFileFavorites);
        String path = userDownloadDataDir + File.separator + EXPORT_PRJ_NAME + "_" + nowTimeStr + File.separator + ED_FILE_FAVORITE + ".json";
        fileSystemService.writeStringToFile(path, json);
    }

    private List<String> exportPrjInfo(String nowTimeStr, int dataOwnCode, String dataIdArr, String userDownloadDataDir) {
        String[] ids = dataIdArr.split(",");
        if (DataOwnEnum.isSysCode(dataOwnCode) || DataOwnEnum.isRepoCode(dataOwnCode)) {
            Map<String, Boolean> map = permissionService.filterExportIds(ids);
            Assert.isTrue(!map.containsValue(Boolean.FALSE), "有未授权的层级目录，禁止导出");
        }
        Map<String, EdFileInfo> maps = new HashMap<>();
        for (String id : ids) {
            Map<String, EdFileInfo> edFileInfos = this.baseMapper.selectList(Wrappers.lambdaQuery(EdFileInfo.class)
                            .like(EdFileInfo::getFilePath, MYSQL_FILE_PATH_SPLIT + id + MYSQL_FILE_PATH_SPLIT))
                    .stream().collect(Collectors.toMap(EdFileInfo::getId, e -> e));
            maps.putAll(edFileInfos);
        }
        Set<EdFileInfo> resFiles = new HashSet<>(maps.values());

        String prjId = resFiles.stream().findFirst().get().getFilePath().split(MYSQL_FILE_PATH_SPLIT)[0];
        List<EdFileInfo> prjFolders = this.baseMapper.selectList(Wrappers.lambdaQuery(EdFileInfo.class)
                .likeRight(EdFileInfo::getFilePath, prjId + MYSQL_FILE_PATH_SPLIT)
                .eq(EdFileInfo::getDataType, EleDataTypeEnum.FOLDER.code)
                .eq(EdFileInfo::getDataOwn, DataOwnEnum.getPrjCodeByFileCode(dataOwnCode))
                .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
        EdFileInfo prjFileInfo = this.baseMapper.selectById(prjId);
        Map<String, EdFileInfo> prjFoldersMap = prjFolders.stream().collect(Collectors.toMap(EdFileInfo::getId, e -> e));
        Map<String, EdFileInfo> tmps = resFiles.stream().collect(Collectors.toMap(EdFileInfo::getId, e -> e));
        tmps.putAll(prjFoldersMap);
        resFiles.clear();
        resFiles.addAll(tmps.values());
        resFiles.add(prjFileInfo);

        List<EdFileInfo> files = resFiles.stream().filter(e -> e.getDataType().equals(EleDataTypeEnum.FILE.code)).toList();
        for (EdFileInfo edFileInfo : files) {
            String filePath = commonService.getFileSysPath(edFileInfo.getId()); // file
            String destPath = userDownloadDataDir + File.separator + EXPORT_PRJ_NAME + "_" + nowTimeStr + File.separator + EXPORT_PRJ_NAME + File.separator + edFileInfo.getId();
            fileSystemService.copyFile(filePath, destPath);
        }
        // 去除用户创建的空文件夹
        List<EdFileInfo> userFolders = resFiles.stream().filter(e ->
                e.getDataType().equals(EleDataTypeEnum.FOLDER.code) && DataOwnEnum.isFileCode(e.getDataOwn())
        ).toList();
        List<String> userFolderIds = userFolders.stream().map(EdFileInfo::getId).collect(Collectors.toList());
        List<String> fileParentIds = files.stream().map(EdFileInfo::getParentId).toList();
        userFolderIds.removeAll(fileParentIds);
        resFiles = resFiles.stream().filter(e -> !userFolderIds.contains(e.getId())).collect(Collectors.toSet());

        String json = JSONUtil.toJsonStr(resFiles);
        String mysqlFilePath = userDownloadDataDir + File.separator + EXPORT_PRJ_NAME + "_" + nowTimeStr + File.separator + PRJ_INFO + ".json";
        fileSystemService.writeStringToFile(mysqlFilePath, json);
        return files.stream().map(EdFileInfo::getId).toList();
    }

    /**
     * 文件上传
     *
     * @param parentId
     * @param file
     * @param strategy 1-跳过冲突文件 2-做版本更新 3-重命名，文件名加“_1"
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ElectromagneticResult<?> upload(String parentId, MultipartFile file, Integer strategy, int dataOwnCode) {
        return commonService.upload(parentId, file, strategy, dataOwnCode);
    }

    /**
     * 版本查看
     *
     * @param fileId
     * @return
     */
    @Override
    public ElectromagneticResult<?> versionView(String fileId) {
        List<EdFileInfo> edFileInfos = this.baseMapper.selectList(Wrappers.lambdaQuery(EdFileInfo.class)
                .select(EdFileInfo.class, file -> !StrUtil.equals(file.getColumn(), "file_content"))
                .eq(EdFileInfo::getFileId, fileId));
        List<FileVersionViewVO> fileVersionViewVOS = BeanUtil.copyToList(edFileInfos, FileVersionViewVO.class);
        UserThreadLocal.setSuccessInfo(edFileInfos.get(0).getParentId(), fileId, "查询版本信息成功");
        return ElectromagneticResultUtil.success(fileVersionViewVOS);
    }

    /**
     * 移动文件
     *
     * @param id
     * @param targetFolderId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ElectromagneticResult<?> moveFile(String id, String targetFolderId, Integer strategy, int dataOwnCode) {

        Assert.isTrue(FileRepeatEnum.contains(strategy), "解决重名文件参数错误");
        // 获取原文件mysql模型
        EdFileInfo srcFileInfo = this.baseMapper.selectById1(id);
        // 判断目标路径下是否有同名文件，如果所有的同名文件：1）如果所有文件都已经被作废，则该文件为新文件，版本号从100开始。2）如果有没有被作废的文件，则冲突处理方式按---1-跳过冲突文件 2-做版本更新 3-重命名，文件名加"_1"
        long count = this.baseMapper.selectCount(Wrappers.lambdaQuery(EdFileInfo.class)
                .eq(EdFileInfo::getParentId, targetFolderId)
                .eq(EdFileInfo::getFileName, srcFileInfo.getFileName())
                .eq(EdFileInfo::getFileType, srcFileInfo.getFileType())
                .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
        EdFileInfo destFolderInfo = this.baseMapper.selectOne(Wrappers.lambdaQuery(EdFileInfo.class)
                .select(EdFileInfo.class, file -> !StrUtil.equals(file.getColumn(), "file_content"))
                .eq(EdFileInfo::getId, targetFolderId));
        if (count == 0) {
            // 没有同名文件
            // 首先将信息保存到MySQL
            String fileTime = EleCommonUtil.getNowTimeStr();
            String codePathByDbPath = commonService.getCodePathByDbPath(destFolderInfo.getFilePath());
            String newFileCode = commonService.createFileCode(codePathByDbPath, srcFileInfo.getFileType(), FILE_START_VERSION, fileTime);
            srcFileInfo.setParentId(targetFolderId)
                    .setFileVersion(FILE_START_VERSION)
                    .setFileTime(fileTime)
                    .setDataOwn(dataOwnCode)
                    .setFilePath(destFolderInfo.getFilePath() + MYSQL_FILE_PATH_SPLIT + srcFileInfo.getId())
                    .setDataStatus(EleDataStatusEnum.OCCUPY.code)
                    .setFileCode(newFileCode);
            this.baseMapper.updateById(srcFileInfo);
        } else {
            srcFileInfo = handMoveConflict(targetFolderId, strategy, srcFileInfo, destFolderInfo, dataOwnCode);
//            srcFileInfo.setDataStatus(EleDataStatusEnum.OCCUPY.code);
//            this.baseMapper.updateById(srcFileInfo);
        }
        UserThreadLocal.setSuccessInfo(srcFileInfo.getParentId(), srcFileInfo.getFileId(), "文件 {} 移动到 {}，成功，处理文件同名同后缀的方式为 {}，最终文件名为 {}", srcFileInfo.getFileName() + "." + srcFileInfo.getFileName(),
                commonService.getDbPath(destFolderInfo.getFilePath()), FileRepeatEnum.getDesc(strategy), srcFileInfo.getFileName());
        return ElectromagneticResultUtil.success(true);
    }

    private EdFileInfo handMoveConflict(String targetFolderId, Integer strategy, EdFileInfo srcFileInfo, EdFileInfo destFolderInfo, int dataOwnCode) {

        // 禁止同目录下移动和复制
        if (srcFileInfo.getParentId().equals(destFolderInfo.getId())) {
            String info = "禁止相同文件夹下移动文件";
            log.info(info);
            throw new BizException(info);
        }

        if (strategy == IGNORE.code) {
            return srcFileInfo;
        } else if (strategy == REVERSION.code) {
            // 做版本更新
            List<EdFileInfo> sameFileInfos = this.baseMapper.selectList(Wrappers.lambdaQuery(EdFileInfo.class)
                    .eq(EdFileInfo::getParentId, targetFolderId)
                    .eq(EdFileInfo::getFileName, srcFileInfo.getFileName())
                    .eq(EdFileInfo::getFileType, srcFileInfo.getFileType()));
            Integer maxFileVersion = Collections.max(sameFileInfos, Comparator.comparing(EdFileInfo::getFileVersion)).getFileVersion();
            String newFileDbId = EleIdCreator.createSnowflakeId();
            String fileTime = EleCommonUtil.getNowTimeStr();
            String codePathByDbPath = commonService.getCodePathByDbPath(destFolderInfo.getFilePath());
            String fileCode = commonService.createFileCode(codePathByDbPath, srcFileInfo.getFileType(), maxFileVersion + 1, fileTime);
            EdFileInfo fileInfoTmp = sameFileInfos.stream().filter(e -> e.getEffectFlag().equals(EffectFlagEnum.EFFECT.code)).findFirst().orElseThrow(RuntimeException::new);
            EdFileInfo destSaveFileInfo = BeanUtil.copyProperties(fileInfoTmp, EdFileInfo.class);
            destSaveFileInfo.setId(newFileDbId);
            destSaveFileInfo.setFileVersion(maxFileVersion + 1)
                    .setFilePath(destFolderInfo.getFilePath() + MYSQL_FILE_PATH_SPLIT + newFileDbId)
                    .setPreVersion(maxFileVersion)
                    .setDataStatus(EleDataStatusEnum.PUBLISHED.code)
                    .setDataOwn(dataOwnCode)
                    .setFileCode(fileCode)
                    .setEffectFlag(EffectFlagEnum.EFFECT.code);


            this.baseMapper.insert(destSaveFileInfo);
            this.baseMapper.update(new EdFileInfo(), Wrappers.lambdaUpdate(EdFileInfo.class)
                    .set(EdFileInfo::getEffectFlag, EffectFlagEnum.NOT_EFFECTIVE.code)
                    .eq(EdFileInfo::getId, fileInfoTmp.getId()));
            String srcPath = commonService.getFileSysPath(srcFileInfo.getId());
            String destPath = commonService.getFileSysPath(destSaveFileInfo.getId());
            fileSystemService.copyFile(srcPath, destPath);
            this.baseMapper.deleteById(srcFileInfo.getId());
            return destSaveFileInfo;
        } else if (strategy == NEW.code) {
            // 文件名加“_1”，版本号从100开始
            // 处理MySQL相关逻辑
            EdFileInfo newEdFileInfo = BeanUtil.copyProperties(srcFileInfo, EdFileInfo.class);
            newEdFileInfo.newInit();
            String dbPathByDbPath = commonService.getCodePathByDbPath(destFolderInfo.getFilePath());
            newEdFileInfo.setParentId(targetFolderId)
                    .setDataStatus(EleDataStatusEnum.PUBLISHED.code)
                    .setFileVersion(FILE_START_VERSION)
                    .setDataOwn(dataOwnCode)
                    .setFileName(srcFileInfo.getFileName() + APPEND_NEW_FILE_NAME)
                    .setFileCode(commonService.createFileCode(dbPathByDbPath, srcFileInfo.getFileType(), FILE_START_VERSION, newEdFileInfo.getFileTime()))
                    .setFilePath(destFolderInfo.getFilePath() + MYSQL_FILE_PATH_SPLIT + newEdFileInfo.getId());
            resetFileInfoName(newEdFileInfo);
            this.baseMapper.insert(newEdFileInfo);
            String srcPath = commonService.getFileSysPath(srcFileInfo.getId());
            String destPath = commonService.getFileSysPath(newEdFileInfo.getId());
            fileSystemService.copyFile(srcPath, destPath);
            this.baseMapper.deleteById(srcFileInfo.getId());
            return newEdFileInfo;
        } else {
            throw new BizException("参数错误");
        }
    }

    /**
     * 复制文件
     *
     * @param id
     * @param targetFolderId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ElectromagneticResult<?> copyFile(String id, String targetFolderId, Integer strategy, int dataOwnCode) {
        this.baseMapper.update(Wrappers.lambdaUpdate(EdFileInfo.class)
                .set(EdFileInfo::getDataStatus, EleDataStatusEnum.OCCUPY.code)
                .eq(EdFileInfo::getId, id));
        Assert.isTrue(FileRepeatEnum.contains(strategy), "解决重名文件参数错误");
        // 获取原文件mysql模型
        EdFileInfo srcFileInfo = this.baseMapper.selectById1(id);
        // 判断目标路径下是否有同名文件，如果所有的同名文件：1）如果所有文件都已经被作废，则该文件为新文件，版本号从100开始。2）如果有没有被作废的文件，则冲突处理方式按---1-跳过冲突文件 2-做版本更新 3-重命名，文件名加"_1"
        long count = this.baseMapper.selectCount(Wrappers.lambdaQuery(EdFileInfo.class)
                .eq(EdFileInfo::getParentId, targetFolderId)
                .eq(EdFileInfo::getFileName, srcFileInfo.getFileName())
                .eq(EdFileInfo::getFileType, srcFileInfo.getFileType())
                .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
        EdFileInfo destFolderInfo = this.baseMapper.selectById(targetFolderId);
        EdFileInfo destFileInfo;

        if (count == 0) {
            // 没有同名文件
            // 首先将信息保存到MySQL
            String fileTime = EleCommonUtil.getNowTimeStr();
            String codePathByDbPath = commonService.getCodePathByDbPath(destFolderInfo.getFilePath());
            String newFileCode = commonService.createFileCode(codePathByDbPath, srcFileInfo.getFileType(), FILE_START_VERSION, fileTime);
            destFileInfo = BeanUtil.copyProperties(srcFileInfo, EdFileInfo.class);
            destFileInfo.newInit();
            destFileInfo.setParentId(targetFolderId)
                    .setDataStatus(EleDataStatusEnum.PUBLISHED.code)
                    .setFileVersion(FILE_START_VERSION)
                    .setFileTime(fileTime)
                    .setFilePath(destFolderInfo.getFilePath() + MYSQL_FILE_PATH_SPLIT + destFileInfo.getId())
                    .setFileCode(newFileCode);
            this.baseMapper.insert(destFileInfo);
            String srcPath = commonService.getFileSysPath(srcFileInfo.getId());
            String destPath = commonService.getFileSysPath(destFileInfo.getId());
            fileSystemService.copyFile(srcPath, destPath);
        } else {
            destFileInfo = handCopyConflict(targetFolderId, strategy, srcFileInfo, destFolderInfo);
        }
        UserThreadLocal.setSuccessInfo(destFileInfo.getParentId(), destFileInfo.getFileId(), "文件 {} 复制到 {}，成功，处理文件同名同后缀的方式为 {}，最终文件名为 {}", destFileInfo.getFileName() + "." + destFileInfo.getFileName(),
                commonService.getDbPath(destFolderInfo.getFilePath()), FileRepeatEnum.getDesc(strategy), srcFileInfo.getFileName());
        return ElectromagneticResultUtil.success(true);
    }

    /**
     * 发布管理
     *
     * @return
     */
    @Override
    public ElectromagneticResult<?> uploadRecord(int pageNum, int pageSize, int dataOwnCode) {

        LambdaQueryWrapper<EdFileInfo> lambdaQuery = Wrappers.lambdaQuery(EdFileInfo.class)
                .select(EdFileInfo.class, file -> !StrUtil.equals(file.getColumn(), "file_content"))
                .eq(EdFileInfo::getDataOwn, dataOwnCode)
                .eq(EdFileInfo::getDataType, EleDataTypeEnum.FILE.code)
                .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code);

        if (dataOwnCode == USER_FILE.code) {
            lambdaQuery.eq(EdFileInfo::getCreatedBy, UserThreadLocal.getUserId());
        } else {
            List<String> accessibleIds = permissionService.getAccessibleTree();
            Set<String> allLeafIds = commonService.selectPrjLeafs(dataOwnCode, accessibleIds);
            List<String> list = new ArrayList<>();
            for (String leafId : allLeafIds) {
                if (accessibleIds.contains(leafId)) {
                    list.add(leafId);
                }
            }
            if (CollUtil.isEmpty(list)) {
                UserThreadLocal.setSuccessInfo("", "", "查看发布管理成功");
                return ElectromagneticResultUtil.success(new UploadRecordVO(0, new ArrayList<>()));
            }
            lambdaQuery.or(qr -> {
                for (String id : list) {
                    qr.likeRight(EdFileInfo::getFileCode, id);
                }
            });
        }

        Page<EdFileInfo> edFileInfoPage = this.baseMapper.selectPage(new Page<>(pageNum, pageSize), lambdaQuery);
        long total = edFileInfoPage.getTotal();
        List<UploadRecordDTO> uploadRecordDTOS = BeanUtil.copyToList(edFileInfoPage.getRecords(), UploadRecordDTO.class);
        UserThreadLocal.setSuccessInfo("", "", "查看发布管理成功");
        return ElectromagneticResultUtil.success(new UploadRecordVO(total, uploadRecordDTOS));
    }

    /**
     * 查询文件详情
     *
     * @param id
     * @return
     */
    @Override
    public ElectromagneticResult<?> detail(String id) {
        EdFileInfo fileInfo = this.baseMapper.selectById(id);
        User singleUser = userMapper.getSingleUser(fileInfo.getCreatedBy());
        fileInfo.setCreatedBy(singleUser.getUserName());
        FileInfoVO fileInfoVO = BeanUtil.copyProperties(fileInfo, FileInfoVO.class);
        fileInfoVO.setFileSizeShow(EleCommonUtil.convertFileSize(fileInfoVO.getFileSize()));
        fileInfoVO.setLabels(fileTagRelationService.getFileTags(fileInfo.getFileId()));
        String filePath = commonService.getDbPath(fileInfo.getFilePath());
        UserThreadLocal.setSuccessInfo(fileInfo.getParentId(), id, "查询了文件的详细信息，文件id {}，文件路径 {}", id, filePath);
        return ElectromagneticResultUtil.success(fileInfoVO);
    }

    private EdFileInfo handCopyConflict(String targetFolderId, Integer strategy, EdFileInfo srcFileInfo, EdFileInfo destFolderInfo) {
        // 禁止同目录下移动和复制
        if (srcFileInfo.getParentId().equals(destFolderInfo.getId())) {
//            srcFileInfo.setDataStatus(EleDataStatusEnum.PUBLISHED.code);
//            this.baseMapper.updateById(srcFileInfo);
            String info = "禁止相同文件夹下复制文件";
            log.info(info);
            throw new BizException(info);
        }

        if (strategy == IGNORE.code) {
            return destFolderInfo;
        }
        if (strategy == REVERSION.code) {
            // 做版本更新
            LambdaQueryWrapper<EdFileInfo> queryWrapper = Wrappers.lambdaQuery(EdFileInfo.class)
                    .eq(EdFileInfo::getParentId, targetFolderId)
                    .eq(EdFileInfo::getFileName, srcFileInfo.getFileName())
                    .eq(EdFileInfo::getFileType, srcFileInfo.getFileType());
            List<EdFileInfo> sameFileInfos = this.baseMapper.selectList(queryWrapper);
            Integer maxFileVersion = Collections.max(sameFileInfos, Comparator.comparing(EdFileInfo::getFileVersion)).getFileVersion();
            String newFileDbId = EleIdCreator.createSnowflakeId();
            String fileTime = EleCommonUtil.getNowTimeStr();
            String codePathByDbPath = commonService.getCodePathByDbPath(destFolderInfo.getFilePath());
            String fileCode = commonService.createFileCode(codePathByDbPath, srcFileInfo.getFileType(), maxFileVersion + 1, fileTime);
            EdFileInfo fileInfoTmp = sameFileInfos.stream().filter(e -> e.getEffectFlag().equals(EffectFlagEnum.EFFECT.code)).findFirst().orElseThrow(RuntimeException::new);
            EdFileInfo destSaveFileInfo = BeanUtil.copyProperties(fileInfoTmp, EdFileInfo.class);
            destSaveFileInfo.setId(newFileDbId);
            destSaveFileInfo.setFileVersion(maxFileVersion + 1)
                    .setFilePath(destFolderInfo.getFilePath() + MYSQL_FILE_PATH_SPLIT + newFileDbId)
                    .setPreVersion(maxFileVersion)
                    .setFileCode(fileCode)
                    .setDataStatus(EleDataStatusEnum.PUBLISHED.code)
                    .setEffectFlag(EffectFlagEnum.EFFECT.code);
            this.baseMapper.update(new EdFileInfo(), Wrappers.lambdaUpdate(EdFileInfo.class)
                    .eq(EdFileInfo::getParentId, targetFolderId)
                    .eq(EdFileInfo::getFileName, srcFileInfo.getFileName())
                    .eq(EdFileInfo::getFileType, srcFileInfo.getFileType())
                    .set(EdFileInfo::getEffectFlag, EffectFlagEnum.NOT_EFFECTIVE.code));
            this.baseMapper.insert(destSaveFileInfo);
            String srcPath = commonService.getFileSysPath(srcFileInfo.getId());
            String destPath = commonService.getFileSysPath(destSaveFileInfo.getId());
            fileSystemService.copyFile(srcPath, destPath);
            return destSaveFileInfo;
        } else if (strategy == FileRepeatEnum.NEW.code) {
            // 文件名加“_1”，版本号从100开始
            // 处理MySQL相关逻辑
            EdFileInfo newEdFileInfo = BeanUtil.copyProperties(srcFileInfo, EdFileInfo.class);
            newEdFileInfo.newInit();
            String dbPathByDbPath = commonService.getCodePathByDbPath(destFolderInfo.getFilePath());
            newEdFileInfo.setParentId(targetFolderId)
                    .setDataStatus(EleDataStatusEnum.PUBLISHED.code)
                    .setFileVersion(FILE_START_VERSION)
                    .setFileName(srcFileInfo.getFileName() + APPEND_NEW_FILE_NAME)
                    .setFileCode(commonService.createFileCode(dbPathByDbPath, srcFileInfo.getFileType(), FILE_START_VERSION, newEdFileInfo.getFileTime()))
                    .setFilePath(destFolderInfo.getFilePath() + MYSQL_FILE_PATH_SPLIT + newEdFileInfo.getId());
            resetFileInfoName(newEdFileInfo);
            this.baseMapper.insert(newEdFileInfo);
            String srcPath = commonService.getFileSysPath(srcFileInfo.getId());
            String destPath = commonService.getFileSysPath(newEdFileInfo.getId());
            fileSystemService.copyFile(srcPath, destPath);
            return newEdFileInfo;
        } else {
            throw new BizException("参数错误");
        }
    }

    private String doSysFileMerge(String identifier, String fileName, Integer totalChunks, int dataOwnCode) {
        String currentUserId = UserThreadLocal.getUserId();
        for (int i = 1; i <= totalChunks; i++) {
            String tmpPath = elePropertyConfig.getUploadDataDir(dataOwnCode) + File.separator + currentUserId + File.separator + identifier + File.separator + i + UPLOAD_FILE_CHUNK_SUFFIX;
            if (!FileUtil.exist(new File(tmpPath))) {
                String info = StrFormatter.format("第{}个分片没有上传完成，请上传完成后再合并。", i);
                log.error(info);
                throw new BizException(info);
            }
        }
        // 合并分片
        String destColibPath = elePropertyConfig.getUploadDataDir(dataOwnCode) + File.separator + currentUserId + File.separator + fileName;
        String path = elePropertyConfig.getUploadDataDir(dataOwnCode) + File.separator + currentUserId + File.separator + identifier;
        BufferedOutputStream outputStream = FileUtil.getOutputStream(destColibPath);
        for (int i = 1; i <= totalChunks; i++) {
            try (BufferedInputStream inputStream = FileUtil.getInputStream(path + File.separator + i + UPLOAD_FILE_CHUNK_SUFFIX)) {
                IoUtil.copy(inputStream, outputStream);
            } catch (Exception e) {
                FileUtil.del(destColibPath);
                String info = "文件合并失败";
                log.error(info, e);
                throw new BizException(info);
            }
        }
        FileUtil.del(path);
        return destColibPath;
    }

    /**
     * 获取文件的层级Id
     *
     * @param id 文件id
     */
    @Override
    public String getCategoryId(String id) {
        EdFileInfo file = this.baseMapper.selectById(id);
        if (file == null) {
            throw new IllegalArgumentException("此ID未查询到文件：" + id);
        }
        if (file.getId().length() < 6) {
            throw new StringIndexOutOfBoundsException("此文件的FILE_CODE小于六位：" + id);
        }
        if (DataOwnEnum.isPrjCode(file.getDataOwn())) {
            return id;
        }
        return file.getFileCode().substring(0, 6);
    }

    /**
     * 根据父id查询其下自定义的文件夹
     *
     * @param parentId
     * @return
     */
    @Override
    public ElectromagneticResult<?> queryChildFolder(String parentId, int dataOwnCode) {

        List<EdFileInfo> edFileInfos = this.baseMapper.selectList(Wrappers.lambdaQuery(EdFileInfo.class)
                .select(EdFileInfo::getId, EdFileInfo::getFileName)
                .eq(EdFileInfo::getDataType, EleDataTypeEnum.FOLDER.code)
                .eq(EdFileInfo::getDataOwn, dataOwnCode)
                .eq(EdFileInfo::getParentId, parentId)
                .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
        List<ChildFolderVO> res = BeanUtil.copyToList(edFileInfos, ChildFolderVO.class);
        UserThreadLocal.setSuccessInfo(parentId, parentId, "查询了子文件集");
        return ElectromagneticResultUtil.success(res);
    }

    /**
     * 文件预览
     *
     * @param id
     * @param response
     */
    @Override
    public ResponseEntity<InputStreamResource> preview(String id, HttpServletResponse response, int dataOwnCode) {
        String fileSaveTmpPath = null;
        try {
            EdFileInfo fileInfo = this.baseMapper.selectById(id);
            Assert.isTrue(Objects.nonNull(fileInfo), "文件不存在");
            String fileDbPath = commonService.getDbPath(fileInfo.getFilePath());
            String fileSysPath = commonService.getFileSysPath(fileInfo.getId());
            fileSaveTmpPath = elePropertyConfig.getEleTmpPath() + File.separator + IdUtil.fastSimpleUUID() + "." + fileInfo.getFileType();
            FileUtil.copy(fileSysPath, fileSaveTmpPath, true);
            EleCommonUtil.decryptFile(fileSaveTmpPath, SecureUtil.aes(elePropertyConfig.getFileEncPasswd().getBytes()));

            if (Arrays.asList("doc", "docx").contains(fileInfo.getFileType())) {
                String pdfTmpPath = elePropertyConfig.getEleTmpPath() + File.separator + fileInfo.getId() + ".pdf";
                OfficeFileUtil.doc2pdf(fileSaveTmpPath, pdfTmpPath);
                fileSaveTmpPath = pdfTmpPath;
            }

            String mimeType = Files.probeContentType(Paths.get(fileSaveTmpPath));
            mimeType = EleCommonUtil.simpleTrueFalse(ObjectUtil.isNull(mimeType), "application/octet-stream", mimeType);
            FileSystemResource fileSystemResource = new FileSystemResource(fileSaveTmpPath);
            String fileName = fileSystemResource.getFilename();
            HttpHeaders headers = new HttpHeaders();
            InputStream inputStream = fileSystemResource.getInputStream();
            fileName = Base64.encode(fileName.substring(0, fileName.lastIndexOf(".")));
            headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
            headers.add(HttpHeaders.PRAGMA, "no-cache");
            headers.add(HttpHeaders.EXPIRES, "0");
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName);
            headers.add(HttpHeaders.CONTENT_TYPE, mimeType);
            // 构建响应实体(可以返回<byte[]或Resource，返回类型取决body入参类型)
            UserThreadLocal.setSuccessInfo(fileInfo.getParentId(), fileInfo.getFileId(), "文件预览成功，文件名为 {}，路径为 {}", fileInfo.getFileName() + "." + fileInfo.getFileType(), fileDbPath);
            return ResponseEntity
                    .ok()
                    .headers(headers)
                    .contentLength(fileSystemResource.contentLength())
                    .contentType(MediaType.parseMediaType("application/octet-stream;charset=UTF-8"))
                    .body(new InputStreamResource(inputStream));
        } catch (Exception e) {
            String info = StrFormatter.format("文件预览错误，文件id是--->{}，错误信息--->{}", id, e.getMessage());
            log.error(info, e);
            throw new BizException("文件预览错误，文件id是 " + id);
        } finally {
            FileUtil.del(fileSaveTmpPath);
        }
    }

    /**
     * 收藏页 数据查询
     *
     * @param page
     * @param ids
     * @param queryDTO
     * @return
     */
    @Override
    public IPage<FileInfoVO> queryFileList(Page<FileInfoVO> page, List<String> ids, FileInfoQueryDTO queryDTO, int saveStatus, int effectFlag) {
        return this.baseMapper.queryFileList(page, ids, queryDTO, saveStatus, effectFlag);
    }

    /**
     * 添加收藏
     *
     * @param userId
     * @param fileId
     * @return
     */
    @Override
    public boolean addFavorite(String userId, String fileId) {

        // 查询是否已存在
        LambdaQueryWrapper<EdFileFavorite> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(EdFileFavorite::getUserId, userId)
                .eq(EdFileFavorite::getFileId, fileId);
        EdFileFavorite existingFavorite = fileFavoriteService.getOne(queryWrapper);

        boolean result;

        if (existingFavorite != null) {
            // 如果收藏记录已存在且有效
            if (Objects.equals(existingFavorite.getEffectFlag(), EffectFlagEnum.EFFECT.code)) {
                throw new BizException("文件已收藏，无法重复收藏！");
            }
            // 恢复失效的收藏记录
            existingFavorite.setEffectFlag(EffectFlagEnum.EFFECT.code);
            result = fileFavoriteService.updateById(existingFavorite);
        } else {
            // 插入收藏记录
            EdFileFavorite favorite = new EdFileFavorite();
            favorite.newInit();
            favorite.setUserId(userId);
            favorite.setFileId(fileId);
            result = fileFavoriteService.save(favorite);
        }

        if (result) {
            EdFileInfo fileInfo = this.baseMapper.selectById(fileId);
            UserThreadLocal.setSuccessInfo(Optional.ofNullable(fileInfo).map(EdFileInfo::getParentId).orElse(""), fileId, StrFormatter.format("收藏了文件"));
        }
        return result;
    }

    /**
     * 判断是否已收藏
     *
     * @param userId
     * @param fileId
     * @return
     */
    @Override
    public boolean isFavorite(String userId, String fileId) {
        LambdaQueryWrapper<EdFileFavorite> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(EdFileFavorite::getUserId, userId)
                .eq(EdFileFavorite::getFileId, fileId)
                .eq(EdFileFavorite::getEffectFlag, EffectFlagEnum.EFFECT.code);
        return fileFavoriteService.getOne(queryWrapper) != null;
    }

    /**
     * 移除收藏
     *
     * @param userId
     * @param fileId
     * @return
     */
    @Override
    public boolean removeFavorite(String userId, String fileId) {
        boolean isRemoved = fileFavoriteService.logicRemove(userId, fileId);
        if (isRemoved) {
            UserThreadLocal.setSuccessInfo("", fileId, StrFormatter.format("取消了收藏"));
        }
        return isRemoved;
    }

    /**
     * 查询当前用户收藏文件信息
     *
     * @param userId           用户id
     * @param fileInfoQueryDTO 分页信息
     * @return
     */
    @Override
    public IPage<FileInfoVO> findFavorite(String userId, FileInfoQueryDTO fileInfoQueryDTO) {

        List<String> fileIds;

        // 查询收藏文件夹下的文件
        String parentId = fileInfoQueryDTO.getParentId();
        if (StringUtils.isNotBlank(parentId)) {
            LambdaQueryWrapper<EdFileInfo> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(EdFileInfo::getParentId, parentId)
                    .eq(EdFileInfo::getSaveStatus, EleDataSaveStatusEnum.SUCCESS.code)
                    .eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code);
            fileIds = this.baseMapper.selectList(queryWrapper).stream().map(EdFileInfo::getFileId).toList();
        } else {
            LambdaQueryWrapper<EdFileFavorite> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.select(EdFileFavorite::getFileId)
                    .eq(EdFileFavorite::getUserId, userId)
                    .eq(EdFileFavorite::getEffectFlag, EffectFlagEnum.EFFECT.code);
            fileIds = fileFavoriteService.listObjs(queryWrapper, Object::toString);
        }

        // 创建分页对象
        Page<FileInfoVO> page = new Page<>(fileInfoQueryDTO.getPageNum(), fileInfoQueryDTO.getPageSize());
        IPage<FileInfoVO> fileInfoVOIPage = queryFileList(page, fileIds, fileInfoQueryDTO, EleDataSaveStatusEnum.SUCCESS.code, EffectFlagEnum.EFFECT.code);
        fileInfoVOIPage.getRecords().forEach(e -> {
            e.setFileSizeShow(EleCommonUtil.convertFileSize(e.getFileSize()));
            e.setIsFavorite(isFavorite(UserThreadLocal.getUserId(), e.getId()) ? 1 : 0);
            if (String.valueOf(DataOwnEnum.REPO_FILE.code).equals(e.getDataOwn())) {
                e.setIsPersonal(2);
            } else if (String.valueOf(USER_FILE.code).equals(e.getDataOwn()) && Objects.equals(e.getCreatedBy(), UserThreadLocal.getUserId())) {
                e.setIsPersonal(1);
            } else {
                e.setIsPersonal(0);
            }
            // 设置权限
            Map<String, Boolean> permissions = (e.getIsPersonal() == 1)
                    ? permissionService.getPersonalPermission()
                    : permissionService.getUserPermission(UserThreadLocal.getUserId(), getCategoryId(e.getId()), true);
            e.setPermissions(permissions);
        });
        UserThreadLocal.setSuccessInfo("", "", "查询了收藏列表");
        return fileInfoVOIPage;
    }

    /**
     * 文件上传并建立关系
     *
     * @param parentId
     * @param id       主文件Id
     * @param file
     * @param desc     关系描述
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ElectromagneticResult<?> uploadFileAndRelation(String parentId, String id, MultipartFile file, String desc, int dataOwnCode) {

        // 上传文件
        ElectromagneticResult<?> uploadResult = upload(parentId, file, FileRepeatEnum.NEW.code, dataOwnCode);
        Assert.notNull(uploadResult.getData(), "上传失败");
        String relatedFileId = (String) uploadResult.getData();
        // 创建文件关系
        EdFileRelation relation = new EdFileRelation();
        relation.newInit();
        relation.setId1(id);
        relation.setId2(relatedFileId);
        relation.setRelationship(desc);
        edFileRelationService.createRelation(relation);
        UserThreadLocal.setSuccessInfo("", id, StrFormatter.format("建立了文件 {} 与文件 {} 的关系", id, relatedFileId));
        return ElectromagneticResultUtil.success(true);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean flushDataStatus(String id) {
        this.baseMapper.update(Wrappers.<EdFileInfo>lambdaUpdate()
                .set(EdFileInfo::getDataStatus, EleDataStatusEnum.PUBLISHED.code)
                .eq(EdFileInfo::getId, id));
        return true;
    }


    /**
     * 统一废除文件相关数据
     *
     * @param fileId **文件编号，非id
     */
    public void cleanRelatedData(String fileId) {
        // 查询
        LambdaQueryWrapper<EdFileInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(EdFileInfo::getFileId, fileId);
        List<EdFileInfo> fileInfos = this.list(queryWrapper);

        for (EdFileInfo file : fileInfos) {
            String id = file.getId();
            // 逻辑删除 文件收藏关系
            fileFavoriteService.logicRemove(null, id);
            // 逻辑删除 文件权限关系
            rolePermissionService.logicRemove(null, id);
            // 逻辑删除 文件关联关系
            edFileRelationService.logicRemove(id);
        }
        // 逻辑删除 文件标签关系
        fileTagRelationService.logicRemove(fileId, null);
    }
}
